#include "stdafx.h"
#include "ShaderModel.h"
#include "Shader.h"
#include <D3DX10.h>

namespace Reign
{namespace Video
{namespace D3D10
{
	#pragma region Properties
	char* ShaderModel::VariableBufferBytes::get() {return variableBufferBytes;}
	ID3D10ShaderResourceView** ShaderModel::Resources::get() {return resources;}
	#pragma endregion

	#pragma region Constructors
	ShaderModel::ShaderModel(ShaderI^ shader, string^ code, ShaderVersions shaderVersion, ShaderTypes shaderType)
	: Disposable(shader)
	{
		null();
		IntPtr codePtr = IntPtr::Zero, shaderVersionPtr = IntPtr::Zero;

		try
		{
			video = shader->FindParentOrSelfWithException<Video^>();

			codePtr = Marshal::StringToHGlobalAnsi(code);
			string^ shaderLvl = L"";
			switch (shaderVersion)
			{
				case (ShaderVersions::HLSL_4_0): shaderLvl = "_4_0"; break;
				case (ShaderVersions::HLSL_4_1): shaderLvl = "_4_1"; break;
			}
			shaderVersionPtr = Marshal::StringToHGlobalAnsi(shaderType.ToString()->ToLower() + shaderLvl);

			// Code
			ID3D10Blob *codeTEMP, *err = 0;
			if (FAILED(D3DX10CompileFromMemory((char*)codePtr.ToPointer(), code->Length, 0, 0, 0, "main", (char*)shaderVersionPtr.ToPointer(), D3D10_SHADER_OPTIMIZATION_LEVEL3, 0, 0, &codeTEMP, &err, 0)))
			{
				string^ errString;
				if (err)
				{
					errString = gcnew string(static_cast<char*>(err->GetBufferPointer()));
					err->Release();
				}
				Debug::ThrowError(L"ShaderModel", System::String::Format(L"{0} Shader compile error: {1}", shaderType, errString));
			}
			this->code = codeTEMP;

			// Reflection
			ID3D10ShaderReflection* reflectionTEMP;
			D3D10ReflectShader(this->code->GetBufferPointer(), this->code->GetBufferSize(), &reflectionTEMP);
			reflection = reflectionTEMP;

			// Veriable Buffers
			uint constantBufferIndex = 0;
			variables = reflection->GetConstantBufferByIndex(constantBufferIndex);

			D3D10_SHADER_BUFFER_DESC constDesc;
			ZeroMemory(&constDesc, sizeof(D3D10_SHADER_BUFFER_DESC));
			variables->GetDesc(&constDesc);

			if (constDesc.Variables != 0)
			{
				variableBufferBytesCount = constDesc.Size;
				variableBufferBytes = new char[constDesc.Size];
				for (uint i = 0; i != constDesc.Size; ++i) variableBufferBytes[i] = 0;

				ID3D10Buffer* variableBufferTEMP = 0;
				D3D10_BUFFER_DESC cbDesc;
				cbDesc.ByteWidth = constDesc.Size;
				cbDesc.Usage = D3D10_USAGE_DYNAMIC;
				cbDesc.BindFlags = D3D10_BIND_CONSTANT_BUFFER;
				cbDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
				cbDesc.MiscFlags = 0;
				video->Device->CreateBuffer(&cbDesc, 0, &variableBufferTEMP);
				if (variableBufferTEMP == 0)
				{
					Debug::ThrowError(L"ShaderModel", L"Could not create Constant Buffer: " + constantBufferIndex.ToString());
				}
				variableBuffer = variableBufferTEMP;
			}

			// Resources
			D3D10_SHADER_DESC shaderDesc;
			ZeroMemory(&shaderDesc, sizeof(D3D10_SHADER_DESC));
			reflection->GetDesc(&shaderDesc);
			if (shaderDesc.BoundResources != 0)
			{
				resourcesKnownCount = 0;
				resourcesCount = shaderDesc.BoundResources;
				resources = new ID3D10ShaderResourceView*[resourcesCount];
				for (uint i = 0; i != shaderDesc.BoundResources; ++i)
				{
					D3D10_SHADER_INPUT_BIND_DESC inputDesc;
					ZeroMemory(&inputDesc, sizeof(D3D10_SHADER_INPUT_BIND_DESC));
					reflection->GetResourceBindingDesc(i, &inputDesc);
					if (inputDesc.Dimension != D3D10_SRV_DIMENSION_UNKNOWN)
					{
						++resourcesKnownCount;
					}
				}
			}
		}
		catch (Exception^ ex)
		{
			delete this;
			throw ex;
		}
		finally
		{
			if (codePtr != IntPtr::Zero) Marshal::FreeHGlobal(codePtr);
			if (shaderVersionPtr != IntPtr::Zero) Marshal::FreeHGlobal(shaderVersionPtr);
		}
	}

	ShaderModel::~ShaderModel()
	{
		disposeChilderen();
		if (code) code->Release();
		if (resources) delete resources;
		if (reflection) reflection->Release();
		if (variableBufferBytes) delete variableBufferBytes;
		if (variableBuffer) variableBuffer->Release();
		null();
	}

	void ShaderModel::null()
	{
		code = 0;
		resources = 0;
		resourcesCount = 0;
		resourcesKnownCount = 0;
		reflection = 0;
		variableBufferBytes = 0;
		variableBuffer = 0;
	}
	#pragma endregion

	#pragma region Methods
	void ShaderModel::Apply()
	{
		if (variableBuffer)
		{
			void* data;
			variableBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &data);
			memcpy(data, variableBufferBytes, variableBufferBytesCount);
			variableBuffer->Unmap();
		}
	}

	int ShaderModel::Variable(string^ name)
	{
		IntPtr namePtr = Marshal::StringToHGlobalAnsi(name);
		ID3D10ShaderReflectionVariable* variable = variables->GetVariableByName((char*)namePtr.ToPointer());
		Marshal::FreeHGlobal(namePtr);

		D3D10_SHADER_VARIABLE_DESC desc;
		ZeroMemory(&desc, sizeof(D3D10_SHADER_VARIABLE_DESC));
		variable->GetDesc(&desc);
		if (desc.Size != 0)
		{
			return desc.StartOffset;
		}

		return -1;
	}

	int ShaderModel::Resource(string^ name)
	{
		for (uint i = 0; i != resourcesCount; ++i)
		{
			D3D10_SHADER_INPUT_BIND_DESC desc;
			ZeroMemory(&desc, sizeof(D3D10_SHADER_INPUT_BIND_DESC));
			reflection->GetResourceBindingDesc(i, &desc);
			if (desc.Dimension != D3D10_SRV_DIMENSION_UNKNOWN && gcnew string(desc.Name) == name)
			{
				return desc.BindPoint;
			}
		}

		return -1;
	}
	#pragma endregion
}
}
}